﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PvrpSRSolution.cs" company="Luis de la Torre">
//   Copyright Luis de la Torre, unless otherwise indicated
// </copyright>
// <summary>
//   A PVRP solution contains a set of paths of vehicle routes for each day
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace PVRP_SR
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// A PVRP solution contains a set of paths of vehicle routes for each day
    /// </summary>
    public class PvrpSrSolution
    {
        #region Fields

        /// <summary>
        /// The instance.
        /// </summary>
        internal readonly TSPLIBParser Instance;

        /// <summary>
        /// The cost.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", 
            Justification = "Reviewed. Suppression is OK here.")]
        internal double Cost;

        /// <summary>
        /// The distance.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", 
            Justification = "Reviewed. Suppression is OK here.")]
        internal double Distance;

        /// <summary>
        /// Predecessor, indexed by day, vehicle, and node
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", 
            Justification = "Reviewed. Suppression is OK here.")]
        internal int[][][] Predecessors;

        /// <summary>
        /// The routes.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", 
            Justification = "Reviewed. Suppression is OK here.")]
        internal Path[,] Routes; // a set of paths for each day and each vehicle;

        /// <summary>
        /// The special to regular assignments.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", 
            Justification = "Reviewed. Suppression is OK here.")]
        internal Dictionary<int, int[]> StoRAssignments;

        /// <summary>
        /// The successors.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", 
            Justification = "Reviewed. Suppression is OK here.")]
        internal int[][][] Successors;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="PvrpSrSolution"/> class.
        /// </summary>
        /// <param name="instance">
        /// The instance.
        /// </param>
        public PvrpSrSolution(TSPLIBParser instance)
        {
            Debug.Assert(instance != null, "TSPLIB instance is null");
            this.Cost = 0.0;
            this.Distance = 0.0;
            this.Instance = instance;
            this.StoRAssignments = new Dictionary<int, int[]>();
            for (int i = this.Instance.NumNodesRegular; i < this.Instance.NumNodesTotal - 1; i++)
            {
                this.StoRAssignments.Add(i, new int[this.Instance.NumDays]);
            }

            this.Predecessors = new int[this.Instance.NumDays][][];
            this.Successors = new int[this.Instance.NumDays][][];

            for (int i = 0; i < this.Predecessors.Length; i++)
            {
                this.Predecessors[i] = new int[this.Instance.NumVehicles][];
                this.Successors[i] = new int[this.Instance.NumVehicles][];
                for (int j = 0; j < this.Predecessors[0].Length; j++)
                {
                    this.Predecessors[i][j] = new int[this.Instance.NumNodesTotal];
                    this.Successors[i][j] = new int[this.Instance.NumNodesTotal];
                }
            }

            this.Routes = new Path[this.Instance.NumDays, this.Instance.NumVehicles];
            for (int t = 0; t < this.Instance.NumDays; t++)
            {
                for (int j = 0; j < this.Instance.NumVehicles; j++)
                {
                    this.Routes[t, j] = new Path();
                }
            }

            for (int k = 0; k < this.Instance.NumVehicles; k++)
            {
                for (int t = 0; t < this.Instance.NumDays; t++)
                {
                    for (int i = 0; i < this.Instance.NumNodesTotal; i++)
                    {
                        this.Predecessors[t][k][i] = -1;
                        this.Successors[t][k][i] = -1;
                    }
                }
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Check if the insertion of the customer would violate the total route length constraint
        /// </summary>
        /// <param name="node">
        /// the node to insert 
        /// </param>
        /// <param name="day">
        /// the day to insert node 
        /// </param>
        /// <param name="pred">
        /// the predecessor of the node to insert 
        /// </param>
        /// <param name="vehicle">
        /// The vehicle
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public bool CheckInsertion(int node, int day, int pred, int vehicle)
        {
            Debug.Assert(node >= 0 && node <= this.Instance.NumNodesTotal, "node indices outside of range");
            Debug.Assert(day >= 0 && day < this.Instance.NumDays, "day indices outside of range");
            Debug.Assert(this.Routes[day, vehicle].Nodes.Contains(pred), "route does not contain predecessor");
            Debug.Assert(this.Predecessors[day][vehicle][node] == -1, "node should not already have a predecessor");
            Debug.Assert(this.Successors[day][vehicle][node] == -1, "node should not already have a successor");

            // check that the predecessors and the routes are consistent
            Debug.Assert(
                this.Routes[day, vehicle].Nodes.Contains(this.Predecessors[day][vehicle][pred]), 
                "Predecessor not consistent");
            Debug.Assert(
                this.Routes[day, vehicle].Nodes.Contains(this.Successors[day][vehicle][pred]), 
                "Predecessor not consistent");

            // check if the cost of inserting node between pred and its current successor violates the distance constraint
            return this.Routes[day, vehicle].Distance + this.Instance.Distances[pred][node]
                   - this.Instance.Distances[pred][this.Successors[day][vehicle][pred]] <= this.Instance.MaxDistance;
        }

        /// <summary>
        /// Create a solution that has each vehicle visit the depot twice
        /// </summary>
        public void EmptySolution()
        {
            Debug.Assert(this.Instance.StartDepots.Length == 1, "function assumes one depot");
            for (int t = 0; t < this.Instance.NumDays; t++)
            {
                for (int k = 0; k < this.Instance.NumVehicles; k++)
                {
                    this.Routes[t, k].Nodes.Add(this.Instance.StartDepots[0]);
                    this.Routes[t, k].Nodes.Add(this.Instance.StartDepots[0]);
                    this.Predecessors[t][k][this.Instance.StartDepots[0]] = this.Instance.StartDepots[0];
                    this.Successors[t][k][this.Instance.StartDepots[0]] = this.Instance.StartDepots[0];
                }
            }
        }

        /// <summary>
        /// Insert customer in a route according to the InsertionPoint insertion
        /// Update the Route for the insertion
        /// Update predecessors and successors
        /// </summary>
        /// <param name="day">
        /// The day for the insertion 
        /// </param>
        /// <param name="insertion">
        /// specifies the node to insert, the predecessor of the node, and the vehicle
        /// </param>
        public void Insert(int day, InsertionPoint insertion)
        {
            // check that the insertion node is not routed and that predecessor is routed with the predecessor
            Debug.Assert(
                this.Predecessors[day][insertion.InsertionVehicle][insertion.InsertionNode] == -1, 
                "predecessor should be not be declared.");
            Debug.Assert(
                this.Successors[day][insertion.InsertionVehicle][insertion.InsertionNode] == -1, 
                "successor should be not be declared.");
            Debug.Assert(
                !this.Routes[day, insertion.InsertionVehicle].Nodes.Contains(insertion.InsertionNode), 
                "insertion node should not be part of routes.");
            Debug.Assert(
                this.Routes[day, insertion.InsertionVehicle].Nodes.Contains(insertion.InsertionPredecessor), 
                "insertion predecessor should be part of route.");

            // insert vehicle into route after predecessor
            int predecessorIdx =
                this.Routes[day, insertion.InsertionVehicle].Nodes.IndexOf(insertion.InsertionPredecessor);

            this.Routes[day, insertion.InsertionVehicle].Nodes.Insert(predecessorIdx + 1, insertion.InsertionNode);

            // update insertion distance in route
            this.Routes[day, insertion.InsertionVehicle].Cost += insertion.InsertionCost;

            // update insertion cost in route
            this.Routes[day, insertion.InsertionVehicle].Distance += insertion.InsertionDistance;

            // update overall route cost and distance
            this.Cost += insertion.InsertionCost;
            this.Distance += insertion.InsertionDistance;

            // update predecessors and successors
            this.Predecessors[day][insertion.InsertionVehicle][insertion.InsertionNode] = insertion.InsertionPredecessor;
            this.Predecessors[day][insertion.InsertionVehicle][
                this.Successors[day][insertion.InsertionVehicle][insertion.InsertionPredecessor]] =
                insertion.InsertionNode;
            this.Successors[day][insertion.InsertionVehicle][insertion.InsertionNode] =
                this.Successors[day][insertion.InsertionVehicle][insertion.InsertionPredecessor];
            this.Successors[day][insertion.InsertionVehicle][insertion.InsertionPredecessor] = insertion.InsertionNode;
        }

        /// <summary>
        /// Return the cost of inserting node on day after predecessor on route of vehicle 
        /// Cost includes the penalty
        /// </summary>
        /// <param name="node">
        /// The node.
        /// </param>
        /// <param name="day">
        /// The day.
        /// </param>
        /// <param name="pred">
        /// The predecessor.
        /// </param>
        /// <param name="vehicle">
        /// The vehicle.
        /// </param>
        /// <returns>
        /// The <see cref="double"/>.
        /// </returns>
        [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1407:ArithmeticExpressionsMustDeclarePrecedence", Justification = "Reviewed. Suppression is OK here.")]
        public double InsertionCost(int node, int day, int pred, int vehicle)
        {
            Debug.Assert(node >= 0 && node <= this.Instance.NumNodesTotal, "Invalid node index.");
            Debug.Assert(day >= 0 && day < this.Instance.NumDays, "Invalid day index.");
            Debug.Assert(this.Routes[day, vehicle].Nodes.Contains(pred), "Invalid predecessor.");
            Debug.Assert(this.Predecessors[day][vehicle][node] == -1, "Predecessor should be unassigned.");
            Debug.Assert(this.Successors[day][vehicle][node] == -1, "Successor should be unassigned.");

            // new total distance: distance insert node between pred and Successors[pred]
            double insertionDistance = this.InsertionDistance(node, day, pred, vehicle);

            double routeDistance = this.Routes[day, vehicle].Distance + insertionDistance;

            // check if overtime penalty needs to be included
            if (routeDistance > this.Instance.MaxDistance)
            {
                return this.Instance.MaxDistance
                       + this.Instance.OvertimePenalty * (routeDistance - this.Instance.MaxDistance)
                       - this.Routes[day, vehicle].Cost;
            }

            Debug.Assert(routeDistance - this.Routes[day, vehicle].Distance >= 0.0);
            return insertionDistance;
        }

        /// <summary>
        /// The insertion distance.
        /// </summary>
        /// <param name="node">
        /// The node.
        /// </param>
        /// <param name="day">
        /// The day.
        /// </param>
        /// <param name="pred">
        /// The predecessor.
        /// </param>
        /// <param name="vehicle">
        /// The vehicle.
        /// </param>
        /// <returns>
        /// The <see cref="double"/>.
        /// </returns>
        public double InsertionDistance(int node, int day, int pred, int vehicle)
        {
            Debug.Assert(node >= 0 && node < this.Instance.NumNodesTotal, "node index inconsistent");
            Debug.Assert(day >= 0 && day < this.Instance.NumDays, "day index inconsstent");
            Debug.Assert(this.Routes[day, vehicle].Nodes.Contains(pred), "predecessor inconsistent");
            Debug.Assert(this.Predecessors[day][vehicle][node] == -1, "no predecessor found");
            Debug.Assert(this.Successors[day][vehicle][node] == -1, "no successor found");

            // new total distance: distance insert node between pred and Successors[pred]
            return this.Instance.Distances[pred][node]
                   - this.Instance.Distances[pred][this.Successors[day][vehicle][pred]]
                   + this.Instance.Distances[node][this.Successors[day][vehicle][pred]];
        }

        /// <summary>
        /// Check if all nodes scheduled for a day have been scheduled
        /// </summary>
        /// <param name="day">
        /// The day.
        /// </param>
        /// <returns>
        /// Returns true if each customer that demands service on day t is part of a vehicle route
        /// </returns>
        public bool IsDayRouteFeasible(int day)
        {
            for (int i = 1; i < this.Instance.NumNodesRegular; i++)
            {
                if (!(this.Instance.Demands[day][i] >= 0))
                {
                    continue;
                }

                bool routed = this.IsRouted(day, i);

                if (routed == false)
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Check for feasibility: 
        /// all customers are routed in exactly one of the vehicle routes on each day they demand service
        /// </summary>
        /// <returns> true if the route is feasible</returns>
        public bool IsRouteFeasible()
        {
            for (int t = 0; t < this.Instance.NumDays; t++)
            {
                if (!this.IsDayRouteFeasible(t))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Check if node is routed on day
        /// </summary>
        /// <param name="day">
        /// The day.
        /// </param>
        /// <param name="node">
        /// The node.
        /// </param>
        /// <returns>
        /// true if customer node is routed on day
        /// </returns>
        public bool IsRouted(int day, int node)
        {
            for (int k = 0; k < this.Instance.NumVehicles; k++)
            {
                if ((this.Predecessors[day][k][node] >= 0 && node > 0)
                    || (this.Successors[day][k][node] >= 0 && node == 0))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// The print solution.
        /// </summary>
        public void PrintSolution()
        {
#if DEBUG
            double costFromScratch = 0;
            double distanceFromScratch = 0;
#endif
            for (int t = 0; t < this.Instance.NumDays; t++)
            {
                Console.WriteLine("Day: " + t);
                for (int k = 0; k < this.Instance.NumVehicles; k++)
                {
#if DEBUG
                    double routeCostFromScratch = 0;
#endif
                    Console.Write("Route " + k + ": ");
                    foreach (int node in this.Routes[t, k].Nodes)
                    {
                        Console.Write(" " + node);
                    }

                    Console.WriteLine();
                    Console.Write("Route " + k + " from successors: ");
                    bool secondVisitToDepot = false;
                    for (int i = this.Routes[t, k].Nodes[0]; !secondVisitToDepot; i = this.Successors[t][k][i])
                    {
                        // the loop will stop if we have made it through
                        if (i == this.Routes[t, k].Nodes[0])
                        {
                            secondVisitToDepot = true;
                        }

                        Console.Write(" " + i);
#if DEBUG
                        routeCostFromScratch += this.Instance.Distances[i][this.Successors[t][k][i]];
                        distanceFromScratch += this.Instance.Distances[i][this.Successors[t][k][i]];
#endif
                    }

#if DEBUG
                    if (routeCostFromScratch > this.Instance.MaxDistance)
                    {
                        routeCostFromScratch = this.Instance.MaxDistance
                                               +
                                               this.Instance.OvertimePenalty
                                               * (routeCostFromScratch - this.Instance.MaxDistance);
                    }

                    costFromScratch += routeCostFromScratch;
#endif
                    Console.WriteLine();
                }
            }

            Console.WriteLine("cost: " + this.Cost);
            Console.WriteLine("distance: " + this.Distance);

            // cost from scratch
#if DEBUG
            Console.WriteLine("cost from scratch: " + costFromScratch);
            Console.WriteLine("distance from scratch: " + distanceFromScratch);
#endif
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine();
        }

        #endregion
    }
}